package com.shockweb.common.security;

import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.util.Arrays;

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;  
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.spec.SM2ParameterSpec;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex; 
/**
 * sm2工具类
 * @author pengminghua
 *
 */
public class SM2Utils {
	
	

	/**
	 * 生成随机秘钥对  
	 * @return
	 */
    @SuppressWarnings("deprecation")
	public static SM2KeyPair generateKeyPair(){  
        SM2 sm2 = SM2.Instance();  
        AsymmetricCipherKeyPair key = sm2.ecc_key_pair_generator.generateKeyPair();  
        ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) key.getPrivate();  
        ECPublicKeyParameters ecpub = (ECPublicKeyParameters) key.getPublic();  
        BigInteger privateKey = ecpriv.getD();  
        ECPoint publicKey = ecpub.getQ();
        SM2KeyPair keyPair = new SM2KeyPair(publicKey.getEncoded(),privateKey.toByteArray());
        return keyPair;
    }
    
    /**
     * 数据加密 String类型
     * @param publicKeyBase64
     * @param data
     * @param charset
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String encrypt(String publicKeyBase64, String data,String charset) throws SecurityException  {
    	if(data==null) {
    		return null;
    	}
    	try {
    		return Utils.base64EncodeString(encrypt(SM2KeyPair.toPublickey(publicKeyBase64),data.getBytes(charset)));
        }catch(Exception e) {
        	throw new SecurityException("encrypt异常",e);
        }
    }
    
      
    /**
     * 数据加密 byte[]类型
     * @param publicKey
     * @param data
     * @return
     */
    @SuppressWarnings("deprecation")
	public static byte[] encrypt(BCECPublicKey publicKey, byte[] data)  
    {  
        if (publicKey == null)  
        {  
            return null;  
        }  
          
        if (data == null || data.length == 0)  
        {  
            return null;  
        }  
          
        byte[] source = new byte[data.length];  
        System.arraycopy(data, 0, source, 0, data.length);  
          
        Chiper cipher = new Chiper();  
        SM2 sm2 = SM2.Instance();  
        ECPoint c1 = cipher.init_enc(sm2, publicKey.getQ());  
        cipher.encrypt(source);
        byte[] c3 = new byte[32];  
        cipher.dofinal(c3);  

        //C1 C2 C3拼装成加密字串  
        byte[] c1b = c1.getEncoded();
        byte[] result = new byte[c1b.length+source.length+c3.length];
        System.arraycopy(c1b, 0, result, 0, c1b.length);
        
        System.arraycopy(source, 0, result, c1b.length, source.length);
        
        System.arraycopy(c3, 0, result, c1b.length+source.length, c3.length);
        return result;
    }  
    
    /**
     * 数据解密  
     * @param privateKeyBase64
     * @param encryptedDataBase64
     * @param charset
     * @return
     * @throws SecurityException
     */
    public static String decrypt(String privateKeyBase64, String encryptedDataBase64,String charset) throws SecurityException
    {
    	if(encryptedDataBase64==null) {
    		return null;
    	}
    	byte[] data = decrypt(SM2KeyPair.toPrivatekey(privateKeyBase64),Utils.base64Decode(encryptedDataBase64));
    	if(data==null) {
    		return null;
    	}else {
    		try {
    			return new String(data,charset);
	        }catch(Exception e) {
	        	throw new SecurityException("decrypt异常",e);
	        }
    	}
    }
      
    /**
     * 数据解密  
     * @param privateKey
     * @param encryptedData
     * @return
     */
    public static byte[] decrypt(BCECPrivateKey privateKey, byte[] encryptedData) 
    {  
        if (privateKey == null)  
        {  
            return null;  
        }  
          
        if (encryptedData == null || encryptedData.length == 0)  
        {  
            return null;  
        }  
        //加密字节数组转换为十六进制的字符串 长度变为encryptedData.length * 2  
//        String data = Util.base64EncodeString(encryptedData);  
        /***分解加密字串 
         * （C1 = C1标志位2位 + C1实体部分128位 = 130） 
         * （C3 = C3实体部分64位  = 64） 
         * （C2 = encryptedData.length * 2 - C1长度  - C2长度） 
         */  
        byte[] c1Bytes = new byte[65];
        System.arraycopy(encryptedData, 0, c1Bytes, 0, c1Bytes.length);
        int c2Len = encryptedData.length - 97;  
        byte[] c2 = new byte[c2Len];
        System.arraycopy(encryptedData, c1Bytes.length, c2, 0, c2.length);
        byte[] c3 = new byte[32];
        System.arraycopy(encryptedData, encryptedData.length-32, c3, 0, c3.length);
        
        SM2 sm2 = SM2.Instance();  

        //通过C1实体字节来生成ECPoint  
        ECPoint c1 = sm2.ecc_curve.decodePoint(c1Bytes);  
        Chiper cipher = new Chiper();  
        cipher.init_dec(privateKey.getD(), c1);  
        cipher.decrypt(c2);  
        cipher.dofinal(c3);  
        //返回解密结果  
        return c2;  
    }  
    
    /**
     * 签名
     * @param privateKey 私钥
     * @param data 待签名数据
     * @param userId 用户编号
     * @param charset 字符集
     * @return
     * @throws SecurityException
     */
   public static String signSm3WithSm2(PrivateKey privateKey,String data, String userId,String charset)throws SecurityException{
	   if(data==null || userId==null) {
		   return null;
	   }else {
	   		try {
	   			return Utils.base64EncodeString(signSm3WithSm2(privateKey,data.getBytes(charset), userId.getBytes(charset)));
	        }catch(Exception e) {
	        	throw new SecurityException("signSm3WithSm2异常",e);
	        }
	   }
   }
   
    /**
     * 签名
     * @param privateKey
     * @param data
     * @param userId
     * @return
     * @throws SecurityException
     */
    public static byte[] signSm3WithSm2(PrivateKey privateKey,byte[] data, byte[] userId)throws SecurityException{
        return rsAsn1ToPlainByteArray(signSm3WithSm2Asn1Rs(privateKey,data, userId));
    }

    /**
     * BC的SM3withSM2签名得到的结果的rs是asn1格式的，这个方法转化成直接拼接r||s
     * @param rsDer rs in asn1 format
     * @return sign result in plain byte array
     */
    private static byte[] rsAsn1ToPlainByteArray(byte[] rsDer)throws SecurityException{
        ASN1Sequence seq = ASN1Sequence.getInstance(rsDer);
        byte[] r = bigIntToFixexLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(0)).getValue());
        byte[] s = bigIntToFixexLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(1)).getValue());
        byte[] result = new byte[RS_LEN * 2];
        System.arraycopy(r, 0, result, 0, r.length);
        System.arraycopy(s, 0, result, RS_LEN, s.length);
        return result;
    }
    
    private final static int RS_LEN = 32;

    private static byte[] bigIntToFixexLengthBytes(BigInteger rOrS)throws SecurityException{
        // for sm2p256v1, n is 00fffffffeffffffffffffffffffffffff7203df6b21c6052b53bbf40939d54123,
        // r and s are the result of mod n, so they should be less than n and have length<=32
        byte[] rs = rOrS.toByteArray();
        if(rs.length == RS_LEN) return rs;
        else if(rs.length == RS_LEN + 1 && rs[0] == 0) return Arrays.copyOfRange(rs, 1, RS_LEN + 1);
        else if(rs.length < RS_LEN) {
            byte[] result = new byte[RS_LEN];
            Arrays.fill(result, (byte)0);
            System.arraycopy(rs, 0, result, RS_LEN - rs.length, rs.length);
            return result;
        } else {
            throw new SecurityException("err rs: " + Hex.toHexString(rs));
        }
    }
    
    static {
        if (Security.getProvider("BC") == null) {
            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        }
    }
    /**
     *
     * @param msg
     * @param userId
     * @param privateKey
     * @return rs in <b>asn1 format</b>
     */
    public static byte[] signSm3WithSm2Asn1Rs(PrivateKey privateKey,byte[] data, byte[] userId)throws SecurityException{
        try {
            SM2ParameterSpec parameterSpec = new SM2ParameterSpec(userId);
            Signature signer = Signature.getInstance("SM3withSM2", "BC");
            signer.setParameter(parameterSpec);
            signer.initSign(privateKey, new SecureRandom());
            signer.update(data, 0, data.length);
            byte[] sig = signer.sign();
            return sig;
        } catch (Exception e) {
            throw new SecurityException("signSm3WithSm2Asn1Rs异常",e);
        }
    }

    /**
     * 签名
     * @param privateKey 私钥
     * @param data 待签名数据
     * @param userId 用户编号
     * @param charset 字符集
     * @param signBase64 签名值
     * @return
     * @throws SecurityException
     */
   public static boolean verifySm3WithSm2(PublicKey publicKey,String data, String userId,String charset, String signBase64)throws SecurityException{
	   if(data==null || userId==null || signBase64==null) {
		   return false;
	   }else {
	   		try {
	   			return verifySm3WithSm2(publicKey,data.getBytes(charset), userId.getBytes(charset),Utils.base64Decode(signBase64));
	        }catch(Exception e) {
	        	throw new SecurityException("signSm3WithSm2异常",e);
	        }
	   }
   }
   
    /**
     * 验签
     * @param publicKey 公钥 
     * @param data 待签名数据
     * @param userId 用户
     * @param sign 签名值
     * @return
     */
    public static boolean verifySm3WithSm2(PublicKey publicKey,byte[] data, byte[] userId, byte[] sign){
    	if(sign==null) {
    		return false;
    	}
        return verifySm3WithSm2Asn1Rs(publicKey,data, userId, rsPlainByteArrayToAsn1(sign));
    }
    
    /**
     * BC的SM3withSM2验签需要的rs是asn1格式的，这个方法将直接拼接r||s的字节数组转化成asn1格式
     * @param sign in plain byte array
     * @return rs result in asn1 format
     */
    private static byte[] rsPlainByteArrayToAsn1(byte[] sign){
        if(sign.length != RS_LEN * 2) throw new RuntimeException("err rs. ");
        BigInteger r = new BigInteger(1, Arrays.copyOfRange(sign, 0, RS_LEN));
        BigInteger s = new BigInteger(1, Arrays.copyOfRange(sign, RS_LEN, RS_LEN * 2));
        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(new ASN1Integer(r));
        v.add(new ASN1Integer(s));
        try {
            return new DERSequence(v).getEncoded("DER");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 验签
     * @param publicKey
     * @param data
     * @param userId
     * @param sign
     * @return
     */
    public static boolean verifySm3WithSm2Asn1Rs( PublicKey publicKey,byte[] data, byte[] userId, byte[] sign){
        try {
            SM2ParameterSpec parameterSpec = new SM2ParameterSpec(userId);
            Signature verifier = Signature.getInstance("SM3withSM2", "BC");
            verifier.setParameter(parameterSpec);
            verifier.initVerify(publicKey);
            verifier.update(data, 0, data.length);
            return verifier.verify(sign);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}